<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Go 应用代码解析 - Web 服务器与路由</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <header>
        <h1>4. Web 服务器与路由 (使用 Gin)</h1>
        <nav>
            <a href="index.html">概述</a> |
            <a href="01_setup_dependencies.html">1. 依赖与全局变量</a> |
            <a href="02_opentelemetry_init.html">2. OpenTelemetry 初始化</a> |
            <a href="03_database_redis_init.html">3. 数据库与 Redis 初始化</a> |
            <a href="04_web_server_routing.html" class="active">4. Web 服务器与路由</a> |
            <a href="05_request_handlers.html">5. HTTP 请求处理</a> |
            <a href="06_business_logic.html">6. 核心业务逻辑</a> |
            <a href="07_main_startup_shutdown.html">7. 程序启动与关闭</a>
        </nav>
    </header>

    <main>
        <section id="gin-setup">
            <h2>搭建“接待处”：初始化 Gin Web 引擎</h2>
            <p>程序需要一个方式来接收来自互联网的 HTTP 请求（比如用户在浏览器里访问一个网址），并根据请求的地址和类型做出响应。这个“接待处”就是由 Gin 框架来搭建的。</p>
            <p>在 `main` 函数中，有以下几行代码负责设置 Gin：</p>

            <pre><code class="language-go">// (位于 main 函数中)
	log.Println("正在初始化 Gin Web 引擎...")
	// 1. 创建一个新的 Gin 引擎实例
	// gin.New() 创建一个没有任何默认中间件的"干净"引擎
	router := gin.New()

	// 2. 添加中间件 (Middleware)
	// Middleware 就像是在请求到达最终处理函数之前或之后，必须经过的一系列检查点或处理器

	// 添加 OTel 中间件 (非常重要!)
	// "gin-server" 是这个 OTel Span 的名字，代表整个 HTTP 请求的处理过程
	router.Use(otelgin.Middleware("gin-server"))

	// 添加 Gin 自带的 Logger 中间件
	// 会在控制台打印每个请求的日志，包括方法、路径、状态码、耗时等
	router.Use(gin.Logger())

	// 添加 Gin 自带的 Recovery 中间件
	// 如果请求处理过程中发生了 panic (程序崩溃)，这个中间件会捕获它，
	// 防止整个程序挂掉，并返回一个 500 内部服务器错误给客户端
	router.Use(gin.Recovery())

	log.Println("Gin 引擎初始化完成，OTel 和基础中间件已添加。")</code></pre>
            <p><strong>步骤拆解：</strong></p>
            <ol>
                <li><strong>创建 Gin 引擎:</strong> `router := gin.New()` 创建了一个 Gin 的核心对象，我们叫它 `router` (路由器)。`gin.New()` 和 `gin.Default()` 的区别在于 `Default()` 会自动帮你加上 `Logger` 和 `Recovery` 中间件，而 `New()` 则需要你手动添加。这里选择 `New()` 然后手动添加，可以更清晰地看到都加了哪些东西。</li>
                <li><strong>添加中间件 (Middleware):</strong> 中间件是 Gin 框架的一个核心概念。你可以把它想象成流水线上的检查工序。每个请求进来后，会依次通过所有注册的 `.Use()` 中间件，最后才到达处理具体业务的 Handler 函数。响应在返回给客户端之前，也会反向依次穿过这些中间件。
                    <ul>
                        <li><strong>`otelgin.Middleware("gin-server")`</strong>: 这是实现 Web 服务端 OTel 追踪的<strong>关键</strong>！这个由 `otelgin` 包提供的中间件会自动为每个接收到的 HTTP 请求做以下事情：
                            <ul>
                                <li>检查请求头中是否有来自上游服务的追踪信息 (由 Propagator 负责解析，比如 `traceparent` 头)。</li>
                                <li>如果有，就基于这个信息继续当前的 Trace；如果没有，就开始一个新的 Trace。</li>
                                <li>创建一个代表整个 HTTP 请求处理过程的 OTel Span（根 Span 或入口 Span），命名为 "gin-server"。</li>
                                <li>将这个 Span 和 Trace 信息放入请求的 `context.Context` 中，以便后续的 Handler 和 Service 函数可以通过 `ctx` 获取到当前的追踪上下文，并创建它们的子 Span。</li>
                                <li>在请求处理完成后，记录这个 "gin-server" Span 的结束时间、HTTP 状态码、请求路径、方法等信息，并把它标记为结束。</li>
                            </ul>
                            简单说，它负责 OTel 追踪的“开始”和“收尾”工作。
                        </li>
                        <li><strong>`gin.Logger()`</strong>: 这是 Gin 自带的日志记录器。它会把每个请求的基本信息（访问时间、HTTP 方法、URL 路径、响应状态码、处理耗时、客户端 IP）打印到控制台。方便观察服务的访问情况。</li>
                        <li><strong>`gin.Recovery()`</strong>: 这是 Gin 自带的异常恢复机制。如果在处理某个请求时，你的代码不小心触发了 `panic`（一种严重的、可能导致程序崩溃的错误），这个中间件能“接住”这个 `panic`，记录错误信息，并向客户端返回一个标准的 `500 Internal Server Error` 响应，而不是让整个服务进程直接挂掉。这增强了服务的健壮性。</li>
                    </ul>
                </li>
            </ol>
            <p>经过这几步，`router` 就准备好接收请求，并且具备了 OTel 追踪、日志记录和异常恢复的基本能力。</p>
        </section>

        <section id="routing">
            <h2>指路牌：定义 API 路由</h2>
            <p>光有接待处还不够，还需要告诉接待员（Gin 引擎），收到什么样的请求（比如是想查用户还是想创建用户）应该交给谁去处理。这就是定义路由（Routing）。</p>
             <pre><code class="language-go">// (位于 main 函数中)
	log.Println("正在定义 API 路由...")
	// 创建一个路由组 "/users"，所有用户相关的操作都在这个路径下
	userRoutes := router.Group("/users")
	{ // 这个花括号只是为了视觉上的分组，没有语法上的特殊含义
		// 当收到 POST 方法的请求，访问路径是 /users 时，
		// 调用 createUserHandler 这个函数来处理
		userRoutes.POST("", createUserHandler)

		// 当收到 GET 方法的请求，访问路径是 /users/后面跟一个 ID (比如 /users/123) 时，
		// :id 是一个路径参数，具体的值可以在 handler 里获取
		// 调用 getUserHandler 这个函数来处理
		userRoutes.GET("/:id", getUserHandler)
	}

	// 定义一个健康检查的路由
	// 当收到 GET 方法的请求，访问路径是 /health 时，
	// 直接执行一个匿名函数，返回 {"status": "UP"} 的 JSON 响应
	router.GET("/health", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"status": "UP"})
	})
	log.Println("API 路由定义完成。")</code></pre>
            <p><strong>解释：</strong></p>
            <ul>
                <li><strong>`router.Group("/users")`</strong>: 这创建了一个路由分组。所有在这个分组下定义的路由，它们的 URL 路径都会自动加上 `/users` 这个前缀。这样做可以更好地组织相关的 API。</li>
                <li><strong>`userRoutes.POST("", createUserHandler)`</strong>: 定义了一个 POST 请求的路由。
                    <ul>
                        <li>路径是 `""`，结合分组的前缀 `/users`，完整的路径就是 `/users`。</li>
                        <li>HTTP 方法是 `POST`，通常用于创建资源。</li>
                        <li>当匹配到这个路径和方法的请求时，调用 `createUserHandler` 函数进行处理（这个函数我们将在下一节分析）。</li>
                    </ul>
                </li>
                <li><strong>`userRoutes.GET("/:id", getUserHandler)`</strong>: 定义了一个 GET 请求的路由。
                    <ul>
                        <li>路径是 `/:id`，结合分组前缀，完整路径类似于 `/users/abc` 或 `/users/123`。这里的 `:id` 是一个 **路径参数**，表示这部分路径是可变的，并且它的值会被命名为 `id`。在 `getUserHandler` 函数内部，可以通过 `c.Param("id")` 来获取这个具体的值（比如 "abc" 或 "123"）。</li>
                        <li>HTTP 方法是 `GET`，通常用于查询资源。</li>
                        <li>匹配请求时，调用 `getUserHandler` 函数处理（下一节分析）。</li>
                    </ul>
                </li>
                 <li><strong>`router.GET("/health", ...)`</strong>: 定义了一个简单的健康检查接口。
                    <ul>
                        <li>路径是 `/health`，方法是 `GET`。</li>
                        <li>处理函数是一个 **匿名函数** (直接写在路由定义里的函数)。这个函数非常简单，直接返回一个 JSON 对象 `{"status": "UP"}`，并设置 HTTP 状态码为 `200 OK` (`http.StatusOK`)。这个接口通常被外部监控系统（比如 Kubernetes）用来检查服务是否还在正常运行。</li>
                    </ul>
                 </li>
            </ul>
            <p>至此，我们告诉了 Gin 引擎：收到什么样的“信件”（HTTP 请求），应该转交给哪个“部门”（Handler 函数）去处理。</p>
        </section>

        <section id="navigation">
            <h2>下一步</h2>
            <p>路由规则设置好了，是时候看看负责具体处理这些请求的“部门”—— Handler 函数是怎么工作的了。</p>
            <p>➡️ <a href="05_request_handlers.html">点击这里，分析 HTTP 请求处理函数</a></p>
        </section>
    </main>

    <footer>
        <p>Go 应用代码解析</p>
    </footer>
</body>
</html>